/**
 * Title: jFamilyTree GXL provider
 * Description: jFamilyTree GXL graph provider
 * About: Designed and rewritten by Andreas Lundgren, based on the Treebolic application suite
 * Copyright: (c) 2001-2008
 * Terms of use: see license agreement at http://treebolic.sourceforge.net/en/license.htm
 * Author: Bernard Bou
 * Company: bsys
 */
package jFamilyTree.provider.xml.graph.gxl;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import jFamilyTree.model.MenuItem;
import jFamilyTree.model.MountPoint;
import jFamilyTree.model.MutableEdge;
import jFamilyTree.model.Settings;
import jFamilyTree.model.Utils;
import jFamilyTree.model.graph.Graph;
import jFamilyTree.model.graph.GraphEdge;
import jFamilyTree.model.graph.MutableGraph;
import jFamilyTree.model.graph.MutableGraphNode;

/**
 * GXL parser
 * 
 * @author Bernard Bou
 */
public class GxlParser extends jFamilyTree.provider.xml.graph.AbstractParser
{
	/**
	 * Parse graph
	 * 
	 * @return graph if successful
	 */
	protected Graph parseGraph(Document thisDocument)
	{
		MutableGraph thisGraph = new MutableGraph();

		// map
		Hashtable<String, MutableGraphNode> theseGraphNodesById = new Hashtable<String, MutableGraphNode>();

		// enumerate nodes
		NodeList theseNodeNodes = thisDocument.getElementsByTagName("node");
		for (int i = 0; i < theseNodeNodes.getLength(); i++)
		{
			Node thisNodeNode = theseNodeNodes.item(i);
			Element thisNodeElement = (Element) thisNodeNode;

			// get id
			String thisId = thisNodeElement.getAttribute("id");
			if (thisId == null || thisId.isEmpty())
				continue;

			// create node
			MutableGraphNode thisGraphNode = new MutableGraphNode(thisId);
			theseGraphNodesById.put(thisId, thisGraphNode);

			// add to graph
			thisGraph.add(thisGraphNode);

			// attributes
			NodeList theseAttrNodes = thisNodeElement.getElementsByTagName("attr");
			for (int j = 0; j < theseAttrNodes.getLength(); j++)
			{
				Node thisAttrNode = theseAttrNodes.item(j);
				Element thisAttrElement = (Element) thisAttrNode;
				String thisAttributeName = thisAttrElement.getAttribute("name");

				if (thisAttributeName.equalsIgnoreCase("label"))
				{
					Element thisStringElement = getFirstLevel1ElementByTagName(thisAttrElement, "string");
					if (thisStringElement != null)
						thisGraphNode.setLabel(thisStringElement.getTextContent());
				}
				else if (thisAttributeName.equalsIgnoreCase("content"))
				{
					Element thisStringElement = getFirstLevel1ElementByTagName(thisAttrElement, "string");
					if (thisStringElement != null)
						thisGraphNode.setContent(thisStringElement.getTextContent());
				}
				else if (thisAttributeName.equalsIgnoreCase("link"))
				{
					Element thisLocatorElement = getFirstLevel1ElementByTagName(thisAttrElement, "locator");
					if (thisLocatorElement != null)
					{
						String thisHRef = thisLocatorElement.getAttribute("xlink:href");
						thisGraphNode.setLink(thisHRef);
					}
				}
				else if (thisAttributeName.equalsIgnoreCase("img-src"))
				{
					Element thisLocatorElement = getFirstLevel1ElementByTagName(thisAttrElement, "locator");
					if (thisLocatorElement != null)
					{
						String thisHRef = thisLocatorElement.getAttribute("xlink:href");
						thisGraphNode.setImageFile(thisHRef);
					}
				}
				else if (thisAttributeName.equalsIgnoreCase("mountpoint"))
				{
					Element thisLocatorElement = getFirstLevel1ElementByTagName(thisAttrElement, "locator");
					if (thisLocatorElement != null)
					{
						String thisHRef = thisLocatorElement.getAttribute("xlink:href");
						MountPoint.Mounting thisMountPoint = new MountPoint.Mounting();
						thisMountPoint.theURL = thisHRef;
						thisGraphNode.setMountPoint(thisMountPoint);
					}
				}
				else if (thisAttributeName.equalsIgnoreCase("mountpoint-weight"))
				{
					Element thisStringElement = getFirstLevel1ElementByTagName(thisAttrElement, "string");
					if (thisStringElement != null)
					{
						MountPoint.Mounting thisMountPoint = (MountPoint.Mounting) thisGraphNode.getMountPoint();
						if (thisMountPoint != null)
							thisGraphNode.setWeight(Double.valueOf(thisStringElement.getTextContent()));
					}
				}
				else if (thisAttributeName.equalsIgnoreCase("backcolor"))
				{
					Element thisStringElement = getFirstLevel1ElementByTagName(thisAttrElement, "string");
					if (thisStringElement != null)
						thisGraphNode.setBackColor(Utils.stringToColor(thisStringElement.getTextContent()));
				}
				else if (thisAttributeName.equalsIgnoreCase("forecolor"))
				{
					Element thisStringElement = getFirstLevel1ElementByTagName(thisAttrElement, "string");
					if (thisStringElement != null)
						thisGraphNode.setForeColor(Utils.stringToColor(thisStringElement.getTextContent()));
				}
			}
		}

		// EDGES
		NodeList theseEdgeNodes = thisDocument.getElementsByTagName("edge");
		for (int i = 0; i < theseEdgeNodes.getLength(); i++)
		{
			Node thisEdgeNode = theseEdgeNodes.item(i);
			Element thisEdgeElement = (Element) thisEdgeNode;

			// get end ids
			String thisFromId = thisEdgeElement.getAttribute("from");
			if (thisFromId == null)
				continue;
			String thisToId = thisEdgeElement.getAttribute("to");
			if (thisToId == null)
				continue;

			// get ends
			MutableGraphNode thisFromNode = theseGraphNodesById.get(thisFromId);
			if (thisFromNode == null)
				continue;
			MutableGraphNode thisToNode = theseGraphNodesById.get(thisToId);
			if (thisToNode == null)
				continue;

			// try to determine whether this edge is a tree edge
			Boolean isTreeEdge = null;
			Element thisTypeElement = getFirstLevel1ElementByTagName(thisEdgeElement, "type");
			if (thisTypeElement != null)
			{
				String thisType = thisTypeElement.getAttribute("xlink:href");
				if (thisType != null && !thisType.isEmpty())
				{
					if (thisType.equals("schema.xml#TreeEdge"))
						isTreeEdge = true;
					else if (thisType.equals("schema.xml#TreeEdge"))
						isTreeEdge = false;
				}
			}

			// create edge
			MutableEdge thisEdge = new MutableEdge(thisFromNode, thisToNode);

			GraphEdge thisGraphEdge = new GraphEdge(thisFromNode, thisToNode, isTreeEdge);
			thisGraphEdge.setUserData(thisEdge);
			thisGraph.add(thisGraphEdge);

			// attributes
			NodeList theseAttrNodes = thisEdgeElement.getElementsByTagName("attr");
			for (int j = 0; j < theseAttrNodes.getLength(); j++)
			{
				Node thisAttrNode = theseAttrNodes.item(j);
				Element thisAttrElement = (Element) thisAttrNode;
				String thisAttributeName = thisAttrElement.getAttribute("name");
				if (thisAttributeName.equalsIgnoreCase("label"))
				{
					Element thisStringElement = getFirstLevel1ElementByTagName(thisAttrElement, "string");
					if (thisStringElement != null)
						thisEdge.setLabel(thisStringElement.getTextContent());
				}
				else if (thisAttributeName.equalsIgnoreCase("img-src"))
				{
					Element thisLocatorElement = getFirstLevel1ElementByTagName(thisAttrElement, "locator");
					if (thisLocatorElement != null)
					{
						String thisHRef = thisLocatorElement.getAttribute("xlink:href");
						thisEdge.setImageFile(thisHRef);
					}
				}
				else if (thisAttributeName.equalsIgnoreCase("color"))
				{
					Element thisStringElement = getFirstLevel1ElementByTagName(thisAttrElement, "string");
					if (thisStringElement != null)
						thisEdge.setColor(Utils.stringToColor(thisStringElement.getTextContent()));
				}
				else if (thisAttributeName.equals("stroke"))
				{
					Element thisStringElement = getFirstLevel1ElementByTagName(thisAttrElement, "string");
					if (thisStringElement != null)
						thisEdge.setStyle(Utils.modifyStyle(thisEdge.getStyle(), thisStringElement.getTextContent(), Utils.StyleComponent.STROKE));
				}
				else if (thisAttributeName.equals("fromterminator"))
				{
					Element thisStringElement = getFirstLevel1ElementByTagName(thisAttrElement, "string");
					if (thisStringElement != null)
						thisEdge.setStyle(Utils.modifyStyle(thisEdge.getStyle(), thisStringElement.getTextContent(), Utils.StyleComponent.FROMTERMINATOR));
				}
				else if (thisAttributeName.equals("toterminator"))
				{
					Element thisStringElement = getFirstLevel1ElementByTagName(thisAttrElement, "string");
					if (thisStringElement != null)
						thisEdge.setStyle(Utils.modifyStyle(thisEdge.getStyle(), thisStringElement.getTextContent(), Utils.StyleComponent.TOTERMINATOR));
				}
			}
		}
		return thisGraph;
	}

	/**
	 * Parse settings
	 * 
	 * @return graph if successful
	 */
	protected Settings parseSettings(Document thisDocument)
	{
		Settings theseSettings = new Settings();
		theseSettings.theHasToolbarFlag = true;
		theseSettings.theHasStatusbarFlag = true;

		Element thisGraphElement = getFirstElementByTagName(thisDocument.getDocumentElement(), "graph");
		if (thisGraphElement != null)
		{
			// enumerate attrs
			List<Element> theseAttrElements = getLevel1ChildElementsByTagName(thisGraphElement, "attr");
			for (Element thisAttrElement : theseAttrElements)
			{
				String thisName = thisAttrElement.getAttribute("name");

				// locator
				Element thisLocatorElement = getFirstLevel1ElementByTagName(thisAttrElement, "locator");
				if (thisLocatorElement != null)
				{
					String thisValue = thisLocatorElement.getAttribute("xlink:href");
					if (thisName.equals("nodes-img-src"))
						theseSettings.theDefaultNodeImage = thisValue;
					else if (thisName.equals("tree-img-src"))
						theseSettings.theBackgroundImage = thisValue;
					else if (thisName.equals("nodes-default-treeedge-img-src"))
						theseSettings.theDefaultTreeEdgeImage = thisValue;
					else if (thisName.equals("edges-default-edge-img-src"))
						theseSettings.theDefaultEdgeImage = thisValue;
				}

				// string
				Element thisStringElement = getFirstLevel1ElementByTagName(thisAttrElement, "string");
				if (thisStringElement != null)
				{
					String thisValue = thisStringElement.getTextContent();
					if (thisName.equals("jFamilyTree-toolbar"))
						theseSettings.theHasToolbarFlag = Boolean.valueOf(thisValue);
					else if (thisName.equals("jFamilyTree-statusbar"))
						theseSettings.theHasStatusbarFlag = Boolean.valueOf(thisValue);
					else if (thisName.equals("jFamilyTree-popupmenu"))
						theseSettings.theHasPopUpMenuFlag = Boolean.valueOf(thisValue);
					else if (thisName.equals("jFamilyTree-tooltip"))
						theseSettings.theHasToolTipFlag = Boolean.valueOf(thisValue);
					else if (thisName.equals("jFamilyTree-focus-on-hover"))
						theseSettings.theFocusOnHoverFlag = Boolean.valueOf(thisValue);

					else if (thisName.equals("tree-backcolor"))
						theseSettings.theBackColor = Utils.stringToColor(thisValue);
					else if (thisName.equals("tree-forecolor"))
						theseSettings.theForeColor = Utils.stringToColor(thisValue);
					else if (thisName.equals("tree-fontface"))
						theseSettings.theFontFace = thisValue;
					else if (thisName.equals("tree-fontsize"))
						theseSettings.theFontSize = Integer.valueOf(thisValue);
					else if (thisName.equals("tree-fontsizestep"))
						theseSettings.theFontSizeStep = Integer.valueOf(thisValue);
					else if (thisName.equals("tree-expansion"))
						theseSettings.theExpansion = Double.valueOf(thisValue);
					else if (thisName.equals("tree-sweep"))
						theseSettings.theSweep = Double.valueOf(thisValue);
					else if (thisName.equals("tree-orientation"))
						theseSettings.theOrientation = thisValue;
					else if (thisName.equals("tree-preserve-orientation"))
						theseSettings.thePreserveOrientationFlag = Boolean.valueOf(thisValue);

					else if (thisName.equals("nodes-backcolor"))
						theseSettings.theNodeBackColor = Utils.stringToColor(thisValue);
					else if (thisName.equals("nodes-forecolor"))
						theseSettings.theNodeForeColor = Utils.stringToColor(thisValue);

					else if (thisName.equals("nodes-default-treeedge-color"))
						theseSettings.theTreeEdgeColor = Utils.stringToColor(thisValue);
					else if (thisName.equals("nodes-default-treeedge-stroke"))
						theseSettings.theTreeEdgeStyle = Utils.modifyStyle(theseSettings.theTreeEdgeStyle, thisValue, Utils.StyleComponent.STROKE);
					else if (thisName.equals("nodes-default-treeedge-fromterminator"))
						theseSettings.theTreeEdgeStyle = Utils.modifyStyle(theseSettings.theTreeEdgeStyle, thisValue, Utils.StyleComponent.FROMTERMINATOR);
					else if (thisName.equals("nodes-default-treeedge-toterminator"))
						theseSettings.theTreeEdgeStyle = Utils.modifyStyle(theseSettings.theTreeEdgeStyle, thisValue, Utils.StyleComponent.TOTERMINATOR);

					else if (thisName.equals("edges-hyperbolic"))
						theseSettings.theHyperbolicEdgesFlag = Boolean.valueOf(thisValue);
					else if (thisName.equals("edges-default-edge-color"))
						theseSettings.theEdgeColor = Utils.stringToColor(thisValue);
					else if (thisName.equals("edges-default-edge-fromterminator"))
						theseSettings.theEdgeStyle = Utils.modifyStyle(theseSettings.theEdgeStyle, thisValue, Utils.StyleComponent.FROMTERMINATOR);
					else if (thisName.equals("edges-default-edge-toterminator"))
						theseSettings.theEdgeStyle = Utils.modifyStyle(theseSettings.theEdgeStyle, thisValue, Utils.StyleComponent.TOTERMINATOR);
					else if (thisName.equals("edges-default-edge-stroke"))
						theseSettings.theEdgeStyle = Utils.modifyStyle(theseSettings.theEdgeStyle, thisValue, Utils.StyleComponent.STROKE);
					else
						System.err.println("Glx: Unhandled attribute: " + thisName);
				}

				// tup
				Element thisTupElement = getFirstLevel1ElementByTagName(thisAttrElement, "tup");
				if (thisTupElement != null)
				{
					List<Element> theseTupleStringElements = getLevel1ChildElementsByTagName(thisTupElement, "string");
					if (theseTupleStringElements != null)
					{
						int k = 0;
						for (Element thisTupleStringElement : theseTupleStringElements)
						{
							if (thisTupleStringElement != null)
							{
								String thisValue = thisTupleStringElement.getTextContent();
								if (thisValue == null)
									continue;

								if (thisName.equals("menuitem-action"))
								{
									theseSettings.theMenu = allocate(theseSettings.theMenu, k);
									MenuItem thisMenuItem = theseSettings.theMenu.get(k);
									thisMenuItem.theAction = Utils.stringToAction(thisValue);
								}
								else if (thisName.equals("menuitem-label"))
								{
									theseSettings.theMenu = allocate(theseSettings.theMenu, k);
									MenuItem thisMenuItem = theseSettings.theMenu.get(k);
									thisMenuItem.theLabel = thisValue;

								}
								else if (thisName.equals("menuitem-match-target"))
								{
									theseSettings.theMenu = allocate(theseSettings.theMenu, k);
									MenuItem thisMenuItem = theseSettings.theMenu.get(k);
									thisMenuItem.theMatchTarget = thisValue;

								}
								else if (thisName.equals("menuitem-match-scope"))
								{
									theseSettings.theMenu = allocate(theseSettings.theMenu, k);
									MenuItem thisMenuItem = theseSettings.theMenu.get(k);
									thisMenuItem.theMatchScope = Utils.stringToScope(thisValue);
								}
								else if (thisName.equals("menuitem-match-mode"))
								{
									theseSettings.theMenu = allocate(theseSettings.theMenu, k);
									MenuItem thisMenuItem = theseSettings.theMenu.get(k);
									thisMenuItem.theMatchMode = Utils.stringToMode(thisValue);
								}
								k++;
							}
						}
					}
				}
			}
		}
		return theseSettings;
	}

	/**
	 * Allocate menu item
	 * 
	 * @param thisList
	 *        current list of menu items
	 * @param i
	 *        ith item to ensure allocation of
	 * @return menu item list on which get(k) will succeed
	 */
	static private List<MenuItem> allocate(List<MenuItem> thisList, int i)
	{
		if (thisList == null)
			thisList = new ArrayList<MenuItem>();
		if (i > thisList.size() - 1)
			thisList.add(new MenuItem());
		return thisList;
	}
}
